package renpyas3.model.structs
{
	import flash.events.IOErrorEvent;
	import flash.media.Sound;
	import flash.net.URLRequest;
	import flash.utils.Dictionary;
	import org.osflash.thunderbolt.Logger;
	
	/**
	 * The label followed by code for a section (text an choices)
	 */
	public class RenpyLabelBlock
	{
		private var _vectorOfLines:Vector.<String>;
		private var _firstLine:uint;
		private var _lastLine:uint;
		private var _name:String;
		private var _nextLabelBlockIndex:int;
		private var _charactersDictionary:/*RenpyCharacter*/Dictionary;
		private var _currInstructionIndex:uint;
		private var _instructions:Vector.<LabelBlockInstruction>;		
		private var _menus:Vector.<RenpyMenu>;
		private var _currentOpenMenu:RenpyMenu; 
		private var _variablesDictionaryPtr:Dictionary;
		private var _soundsDictionaryPtr:/*Sound*/Dictionary;
		private var _path:String;
		private var _openIfOperatorBlock:RenpyIfOperator;
		
		public function RenpyLabelBlock(vectorOfLines:Vector.<String>, path:String, charactersDictionary:/*RenpyCharacter*/Dictionary, variablesDictionaryPtr:/*Boolean*/Dictionary, soundsDictionaryPtr:/*Sound*/Dictionary, firstLine:uint, lastLine:uint, nextLabelBlockIndex:int)  
		{
			_path = path;
			_vectorOfLines = vectorOfLines;
			_charactersDictionary = charactersDictionary;
			_soundsDictionaryPtr = soundsDictionaryPtr;
			_instructions = new Vector.<LabelBlockInstruction>();
			_menus = new Vector.<RenpyMenu>();
			_variablesDictionaryPtr = variablesDictionaryPtr;
			_firstLine = firstLine;
			_lastLine = lastLine;
			_nextLabelBlockIndex = nextLabelBlockIndex;
			//Logger.info("instance RenpyLabelBlock between lines:", _firstLine, " & ", _lastLine);
			parseLabelName(vectorOfLines[_firstLine],_firstLine);
			parseLabelBlock()
		}
		
		/**
		 * go through all the lines finding LabelBlock commands
		 */
		private function parseLabelBlock():void
		{
			var firstWord:String;
			var firstSpace:int;
			var currLine:String
			
			//remove whitespace at the begginning and end
			for (var i:uint = _firstLine+1; i <= _lastLine ; i++ )
			{
				if (_openIfOperatorBlock != null && _openIfOperatorBlock.lineNumberToParseFalse == i)
				{
					//close IfOperator adding the needed line to jump to in case of false
					_openIfOperatorBlock.ifFalseJumpTo = _instructions.length;
					_openIfOperatorBlock = null;
				}
				
				currLine = f_trim(_vectorOfLines[i]);
				if (currLine != "")
				{
					firstSpace = currLine.indexOf(" ");
					//Logger.info("RenpyLabelBlock::parseLabelBlock:"+currLine);
					if (firstSpace != -1)
					{
						firstWord = currLine.substr(0, firstSpace);
						//Logger.info(firstWord);
						parseLine(firstWord, i, currLine);
					}
					else
					{
						//this line must be one word only, so pass the whole line as first word
						parseLine(currLine, i, currLine);
					}
				}
			}
		}

		/**
		 * process only commands understood by LabelBlock
		 */
		private function parseLine(command:String, lineNumber:uint,  currLine:String):void
		{
			//Logger.info("RenpyLabelBlock::parseLine --command:" + command + "<");
			switch(command)
			{
				case "label":
					//parseLabelName(currLine,lineNumber);
					//ignore, label already processed
					_currentOpenMenu = null; //label instructions finish menu instructions
					break;
					
				case "show":
					parseInstructionShow(currLine,lineNumber);
					break;

				case "scene":
					parseInstructionScene(currLine,lineNumber);
					break;
					
				case "with":
					parseTransition(currLine, lineNumber);
					break;
				
				case "play":
					parsePlaySound(currLine, lineNumber);
					break;
					
				case "menu:":
					parseMenu(currLine,lineNumber);
					break;

				case "jump":
					parseJumpDestination(currLine,lineNumber);
					break;

				case "$":
					parseVarChange(currLine, lineNumber);
					break;
					
				case "if":
					parseIfOperator(currLine, lineNumber);
					break;

				case "#":
					break;
					
				case "return":
					_currentOpenMenu = null; //label instructions finish menu instructions
					_instructions.push(new LabelBlockInstruction("return", null, false));

					break;
					
					
				default:
					//detect text/dialogue lines
					//Logger.info("RenpyLabelBlock::parseLine --detect text/dialogue lines:" + command + "<");
					if (_charactersDictionary[command] != null)
					{
						//its definitely text/dialogue
						parseInstructionTextFromCharacter(_charactersDictionary[command], currLine,lineNumber);
					}
					else if ((currLine.charAt(0) == "\"") && (currLine.charAt(currLine.length - 1) == "\""))
					{
						parseInstructionTextFromNarrator(currLine,lineNumber);
					}
					else if ((currLine.charAt(0) == "\"") && (currLine.charAt(currLine.length - 2) == "\"") && (currLine.charAt(currLine.length - 1) == ":"))
					{
						parseChoiceText(currLine,lineNumber);
					}
				break;
				
			}
		}
		
		/**
		 * parse instruction to play music
		 */
		private function parsePlaySound(line:String, lineNumber:uint):void
		{
			//find label to jump to
			
			var musicIndex:int = line.indexOf("music");
			var soundName:String;
			if (musicIndex == -1)
			{
				Logger.error("RenpyLabelBlock::parsePlaySound ERROR expecing 'music' in:"+ line +" -lineNumber:"+lineNumber);
			}
			else
			{
				soundName = line.substr(musicIndex + 5);
				soundName = f_trim(soundName);
				soundName = soundName.replace("\"", ""); //remove first quotation marks
				if (soundName.charAt(soundName.length - 1) == "\"") //remove last quotation marks
				{
					soundName = soundName.substring(0, soundName.length - 1);
				}
				
				Logger.info("RenpyLabelBlock::parsePlaySound:" + soundName + "<"); 
				
				var indexMp3:int = soundName.indexOf("mp3");
				if (indexMp3 != -1)
				{
					try
					{
						var newSound:Sound = new Sound();
						newSound.addEventListener(IOErrorEvent.IO_ERROR, handleLoadSoundFailed);
						newSound.load(new URLRequest(_path + soundName));
						_soundsDictionaryPtr[soundName] = newSound;
						_instructions.push(new LabelBlockInstruction("playMusic", soundName, false));
					}
					catch (e:Error)
					{ 
						trace(e);
					};

				}
				else
				{
					Logger.error("RenpyLabelBlock::parsePlaySound ERROR only mp3 supported --line:"+ line +" -lineNumber:"+lineNumber);
				}
			}
			
		}
		
		private function handleLoadSoundFailed(e:IOErrorEvent):void 
		{
			Logger.error("RenpyLabelBlock::handleLoadSoundFailed ERROR loading",e.text);
		}

		
		/**
		 * parse instruction that changes the value of a variable
		 */
		private function parseVarChange(line:String, lineNumber:uint):void
		{
			//define s = Character('Sylvie', color="#c8ffc8")
			var defName:String;
			var charName:String;
			var charColor:String;
			
			var secondWordIndex:int = line.indexOf(" ");
			var equalsSignIndex:int = line.indexOf("=");
			var defTypeIsChar:int = line.indexOf("Character(", equalsSignIndex);
			var pauseIndex:int = line.indexOf("renpy.pause(");
			var lastParenthesisIndex:int = line.indexOf(")");

			if (pauseIndex != -1 && lastParenthesisIndex != -1)
			{
				//is a pause param for the transition
				//handled by labelblock
				//extract transition name
				var pauseValueAsString:String = line.substr(pauseIndex+12, lastParenthesisIndex -(pauseIndex+12)); //starts after 'with '
				pauseValueAsString = f_trim(pauseValueAsString);
				_instructions.push(new LabelBlockInstruction("pause", pauseValueAsString, false));
				
				Logger.info("RenpyLabelBlock::parseVarChange renpy.pause  --pauseValueAsString:"+pauseValueAsString+ " --as num:"+Number(pauseValueAsString));
				
			}
			else if (secondWordIndex == -1)
			{
				Logger.error("RenpyLabelBlock::parseVarChange ERROR trying to var name in:"+line+" -lineNumber:"+lineNumber);
			}
			else if (equalsSignIndex == -1)
			{
				Logger.error("RenpyLabelBlock::parseVarChange ERROR trying to find = after var name in:"+line+" -lineNumber:"+lineNumber);
			}
			else
			{
				defName = line.substr(secondWordIndex + 1, equalsSignIndex - secondWordIndex - 1);
				defName = f_trim(defName);

				if (defTypeIsChar != -1)
				{
					//this is a character
					Logger.error("RenpyLabelBlock::parseVarChange ERROR not expecting character definition in a LabelBlock:"+line+" -lineNumber:"+lineNumber);
				}
				else
				{
					//is not a character, then it must be a variable
					var varValue:String = line.substr(equalsSignIndex+1);
					varValue = f_trim(varValue);
					
					var params:Object= new Object;
					params["varName"] = defName;
					if (varValue.toLowerCase() == "true")
					{
						params["varValue"] = true;
						_instructions.push(new LabelBlockInstruction("varChange", params, false));
					}
					else if (varValue.toLowerCase() == "false")
					{
						params["varValue"] = false;
						_instructions.push(new LabelBlockInstruction("varChange", params, false));
					}
					else
					{
						Logger.error("RenpyScreen::parseDefinition ERROR not boolean var value  for defName:"+defName+" in:"+line+" -lineNumber:"+lineNumber);
					}
				}
			}
		}
		
		/**
		 * parse line an If instruction
		 */
		private	function parseIfOperator(line:String, lineNumber:uint):void
		{
			var lineWithSpaces:String = _vectorOfLines[lineNumber]
			var tabsCountIfOperatorLine:uint = countSpacesAtStart(lineWithSpaces);
			
			//find next line of code where the whitespaces are the same 
			//as the count in the if operator line. This will be the line to run
			//after the if
			var lineNumberToParseFalse:uint = lineNumber+1;
			var tabsCountIfFalseLine:uint;
			while (lineNumberToParseFalse < _vectorOfLines.length 
			   && countSpacesAtStart(_vectorOfLines[lineNumberToParseFalse]) != tabsCountIfOperatorLine)
			{
				//Logger.info("RenpyLabelBlock::parseIfOperator -finding thisLineNumber, now:", lineNumberToParseFalse, " desiredtabsCountIfOperatorLine:",tabsCountIfOperatorLine, "  --spaces this line:",countSpacesAtStart(_vectorOfLines[lineNumberToParseFalse]));
				lineNumberToParseFalse++;
			}
			
			var ifTrueJumpTo:int = _instructions.length+1; //next instruction the ifOperator instruction I'm about to add
			
			//extract variable name
			var varibleName:String = "";

			line = f_trim(line);
			var secondWordIndex:int = line.indexOf(" ");
			var colonIndex:int = line.indexOf(":");
			if (secondWordIndex == -1)
			{
				Logger.info("RenpyLabelBlock::parseIfOperator ERROR trying to find var name in:"+line+ " -lineNumber:"+lineNumber);
			}
			else if (colonIndex == -1)
			{
				Logger.info("RenpyLabelBlock::parseIfOperator ERROR trying to find colon after var name in:"+line+" -lineNumber:"+lineNumber);
			}
			else
			{
				varibleName = line.substr(secondWordIndex + 1, colonIndex - secondWordIndex-1);
				Logger.info("RenpyLabelBlock::parseIfOperator  --varibleName:"+varibleName+"< --ifTrueJumpTo:"+ifTrueJumpTo+"  --lineNumberToParseFalse:"+lineNumberToParseFalse); 

				//create choice structure and instruction
				_openIfOperatorBlock = new RenpyIfOperator(varibleName, ifTrueJumpTo, lineNumberToParseFalse);
				_instructions.push(new LabelBlockInstruction("ifOperator", _openIfOperatorBlock, false));

			}
		}
		
		/**
		 * counts spaces at the beggining of a line of code
		 * 
		 * @param	line, line of code, String
		 * @return  count spaces at the beggining
		 */
		private function countSpacesAtStart(line:String):uint
		{
			var i:uint = 0;
			var count:uint = 0;
			while(line.charCodeAt(i) == 32)
			{
				i++;
				count++;
				//Logger.info("RenpyLabelBlock::countSpacesAtStart char at i:"+ i +" code:"+lineWithSpaces.charCodeAt(i)+" --as char:"+lineWithSpaces.charAt(i));
			}
			//Logger.info("RenpyLabelBlock::countSpacesAtStart, lineWithSpaces:" + line + "--count:"+count);
			
			return count;
		}
		
		
		/**
		 * parse line inside a menu to jump to another label
		 */
		private function parseJumpDestination(line:String, lineNumber:uint):void
		{
			//find label to jump to
			
			var secondWordIndex:int = line.indexOf(" ");
			var labelName:String;
			if (secondWordIndex == -1)
			{
				Logger.info("RenpyLabelBlock::parseJumpDestination ERROR trying to find second word in:"+line+" -lineNumber:"+lineNumber);
			}
			else
			{
				labelName = line.substr(secondWordIndex + 1);
				labelName = f_trim(labelName);
				//Logger.info("RenpyLabelBlock::parseJumpDestination:"+labelName+"<--push at index:",_instructions.length); 
			}
			
			//if (_currentOpenMenu == null)
			{
				
				_instructions.push(new LabelBlockInstruction("jump", labelName, false));
			}
			//else
			{
				//do nothing here, it belongs to the label of the menu
				//_currentOpenMenu.addJumpDestination(labelName);
			}
		}

		
		/**
		 * parse line to show image and create instruction
		 */
		private function parseInstructionShow(line:String, lineNumber:uint):void
		{
			var secondWordIndex:int = line.indexOf(" ");
			var imageName:String;
			if (secondWordIndex == -1)
			{
				Logger.info("RenpyLabelBlock::parseInstructionShow ERROR trying to find second word in:"+line+" -lineNumber:"+lineNumber);
			}
			else
			{
				imageName = line.substr(secondWordIndex + 1);
				imageName = f_trim(imageName);
				//Logger.info("RenpyLabelBlock::parseInstructionShow:"+imageName); 
			}
			
			var params:Object= new Object;
			params["imageName"] = imageName;
			params["transition"] = "";
			params["transitionPause"] = 0;
			_instructions.push(new LabelBlockInstruction("show", params, false));
		}

		
		/**
		 * create menu
		 */
		private function parseMenu(line:String, lineNumber:uint):void
		{
			var newMenu:RenpyMenu = new RenpyMenu();
			//Logger.info("RenpyLabelBlock::parseMenu called --lineNumber:"+lineNumber);
			
			//this menu will stay open so the next lines will add stuff to it
			//other lines will close it
			_currentOpenMenu = newMenu;
			
			_menus.push(newMenu);
			
			_instructions.push(new LabelBlockInstruction("menu", newMenu, true));
		}

		
		/**
		 * parse line to show background scene and create instruction
		 * is much like parseInstructionShow
		 */
		private function parseInstructionScene(line:String, lineNumber:uint):void
		{
			var secondWordIndex:int = line.indexOf(" ");
			var withIndex:int = line.indexOf(" with "); 
			var imageName:String;
			var color:Number;
			var useColor:Boolean = false;
			
			if (secondWordIndex == -1)
			{
				Logger.info("RenpyLabelBlock::parseInstructionScene ERROR trying to find second word in:"+line+" -lineNumber:"+lineNumber);
			}
			else
			{
				//the line might contain a with [transitioname] instruction or not
				if (withIndex == -1)
				{
					imageName = line.substr(secondWordIndex + 1);
				}
				else
				{
					imageName = line.substr(secondWordIndex + 1, withIndex -(secondWordIndex +1));
				}
				imageName = f_trim(imageName);
				Logger.info("RenpyLabelBlock::parseInstructionScene:"+imageName+"<  withIndex:"+withIndex);
				//some images are in fact color definitions
				switch(imageName)
				{
					case "black":
						color = 0x000000;
						useColor = true;
						imageName = ""; //clear image, will use color instead
						break;
						
					case "white":
						color = 0xFFFFFF;
						useColor = true;
						imageName = ""; //clear image, will use color instead
						break;		
				}
			}
			
			var params:Object= new Object;
			params["imageName"] = imageName;
			params["transition"] = "";
			params["transitionPause"] = 0;
			params["color"] = color;
			params["useColor"] = useColor;
			_instructions.push(new LabelBlockInstruction("scene", params, false));
			
			//the line might contain a with [transitioname] instruction or not
			if (withIndex != -1)
			{
				parseTransition(line, lineNumber);
			}

		}
		
		
		/**
		 * parse transition ('with' instruction) after a scene
		 */
		private function parseTransition(line:String, lineNumber:uint):void
		{
			var withIndex:int = line.indexOf("with "); 
			var transitionName:String;
			if (withIndex == -1)
			{
				Logger.error("RenpyLabelBlock::parseTransition ERROR trying to find second word in:"+line+" -lineNumber:"+lineNumber);
			}
			else
			{
				//extract transition name or parameter
				transitionName = line.substr(withIndex + 5); //starts after 'with '
				transitionName = f_trim(transitionName);
				
				//and apply it to last image
				if (_instructions.length > 0)
				{
					var lastInstr:LabelBlockInstruction = _instructions[_instructions.length - 1];
					var pauseIndex:int = transitionName.indexOf("Pause(");
					var lastParenthesisIndex:int = transitionName.indexOf(")");
					if (lastInstr.name != "scene" && lastInstr.name != "show")
					{
						Logger.error("RenpyLabelBlock::parseTransition ERROR 'with' only recognized for a 'scene' or 'show' in line:"+line+" -lineNumber:"+lineNumber);
					}
					else
					{
						if (pauseIndex != -1 && lastParenthesisIndex != -1)
						{
							//is a pause param for the transition
							//extract transition name
							var pauseValueAsString:String = transitionName.substr(pauseIndex+6, lastParenthesisIndex -(pauseIndex+6)); //starts after 'with '
							pauseValueAsString = f_trim(pauseValueAsString);

							lastInstr.params["transitionPause"] = Number(pauseValueAsString);
							Logger.error("RenpyLabelBlock::parseTransition pause param for the transition:" + transitionName + " to imagename:" + lastInstr.params["imageName"]+ " --pauseValueAsString:"+pauseValueAsString+ " --as num:"+Number(pauseValueAsString));
						}
						else
						{
							Logger.info("RenpyLabelBlock::parseTransition adding transition:" + transitionName + " to imagename:" + lastInstr.params["imageName"]);
							lastInstr.params["transition"] = transitionName;
						}
					}
				}
				else
				{
					Logger.error("RenpyLabelBlock::parseTransition ERROR no previous instruction in line:"+line+" -lineNumber:"+lineNumber);
				}
			}
		}

		
		/**
		 * parse text/dialogue lines voiced by a character 
		 * and create instruction
		 */
		private function parseInstructionTextFromCharacter(renpyCharacter:RenpyCharacter, line:String, lineNumber:uint):void
		{
			var secondWordIndex:int = line.indexOf(" ");
			var text:String;
			if (secondWordIndex == -1)
			{
				Logger.info("RenpyLabelBlock::parseInstructionTextFromCharacter ERROR trying to find second word in:"+line+" -lineNumber:"+lineNumber);
			}
			else
			{
				text = line.substr(secondWordIndex + 1);
				text = f_trim(text);
				text = text.replace("\"", ""); //remove first quotation marks
				if (text.charAt(text.length - 1) == "\"") //remove last quotation marks
				{
					text = text.substring(0, text.length - 1);
				}
				text = text.replace("{i}", "<i>"); //convert italics
				text = text.replace("{/i}", "</i>"); //convert italics
				
				text = removeEscapedQuotationMarks(text);

				Logger.info("RenpyLabelBlock::parseInstructionTextFromCharacter:"+text+"<"); 
			
				var params:Object= new Object;
				params["text"] = text;
				params["char"] = renpyCharacter;
				
				//pass text as param
				_instructions.push(new LabelBlockInstruction("textFromChar", params, true));
			}
		}

			
		/**
		 * parse text/dialogue lines voiced by no one (narrator)
		 * and create instruction
		 */
		private function parseInstructionTextFromNarrator(line:String, lineNumber:uint):void
		{
			line = line.replace("\"", ""); //remove first quotation marks
			if (line.charAt(line.length - 1) == "\"") //remove last quotation marks
			{
				line = line.substring(0, line.length - 1);
			}
			line = line.replace("{i}", "<i>"); //convert italics
			line = line.replace("{/i}", "</i>"); //convert italics
			
			line = removeEscapedQuotationMarks(line);
			Logger.info("RenpyLabelBlock::parseInstructionTextFromNarrator:"+line+"<"); 
						
			//pass text as param
			_instructions.push(new LabelBlockInstruction("textFromNarrator", line, true));
		}
		
		
		/**
		 * parse text/dialogue lines voiced by no one (narrator)
		 * and create instruction
		 */
		private function parseChoiceText(line:String, lineNumber:uint):void
		{
			line = line.replace("\"", ""); //remove first quotation marks
			if (line.charAt(line.length - 1) == ":" && line.charAt(line.length - 2) == "\"") //remove last quotation marks
			{
				line = line.substring(0, line.length - 2);
			}
			line = removeEscapedQuotationMarks(line);
			//Logger.info("RenpyLabelBlock::parseChoiceText:"+line+"<"); 
						
			if (_currentOpenMenu != null)
			{
				//menu open, means that this is a menu option
				_currentOpenMenu.addChoice(line,lineNumber,_instructions.length);
			}
			else
			{
				Logger.info("RenpyLabelBlock::parseChoiceText ERROR no open menu for this choice:"+line+"  --lineNumber:"+lineNumber); 
			}
		}
		
		/**
		 * returns the string with the quotation marks removed
		 */
		private function removeEscapedQuotationMarks(inputString:String):String		{
			while (inputString.indexOf("\\\"") != -1)
			{
				inputString = inputString.replace("\\\"", "\"");
			}
			return inputString;
		}
		
		
		/**
		 * extract label name from line of code
		 */
		private function parseLabelName(line:String, lineNumber:uint):void
		{
			line = f_trim(line);
			var secondWordIndex:int = line.indexOf(" ");
			var colonIndex:int = line.indexOf(":");
			if (secondWordIndex == -1)
			{
				Logger.info("RenpyLabelBlock::parseLabelName ERROR trying to find label name in:"+line+" -lineNumber:"+lineNumber);
			}
			else if (colonIndex == -1)
			{
				Logger.info("RenpyLabelBlock::parseLabelName ERROR trying to find colon after label name in:"+line+" -lineNumber:"+lineNumber);
			}
			else
			{
				_name = line.substr(secondWordIndex + 1, colonIndex - secondWordIndex-1);
				//Logger.info("RenpyLabelBlock::parseLabelName:"+_name+"<"); 
			}
		}
		
		
		/**
		 * start running the instructions in this block
		 */
		public function enterBlock():void
		{
			_currInstructionIndex = 0;
		}

		/**
		 * return next instruction in the list
		 */
		public function getNextInstruction():LabelBlockInstruction
		{
			_currInstructionIndex++
			if (_currInstructionIndex <= _instructions.length)
			{
				return _instructions[_currInstructionIndex - 1];
			}
			else
			{
				return null;
			}
		}
		
		/**
		 * Jump to a given line of code in the list
		 * after a menu choice
		 */
		public function moveInstructionPointerToIndex(nextInstructionIndex:uint):void
		{
			//Logger.info("RenpyLabelBlock::moveInstructionPointerToIndex:" + nextInstructionIndex + "<");
			//Logger.info("RenpyLabelBlock::moveInstructionPointerToIndex nextInstr:", _instructions[nextInstructionIndex].name);// , "  params:", _instructions[nextInstructionIndex].params["text"]);
			_currInstructionIndex = nextInstructionIndex;
		}


		/**
		 * remove whitespace at the begginning and end
		 */
		public static function f_trim(str:String):String
		{
			//remove whitespace
			return(str.replace(/^\s+|\s+$/g, ""));
		}

		public function get name():String { return _name; }
		
		public function get firstLine():uint { return _firstLine; }
		
		/**
		 * next lableBlock in the sequential labelblocks list of the scene
		 */
		public function get nextLabelBlockIndex():int { return _nextLabelBlockIndex; }

		
	}
}